/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.oai.metadataRepository.repository.records.impl;

import cz.mzk.oai.metadataRepository.metadata.DateStamp;
import cz.mzk.oai.metadataRepository.metadata.MetadataFormat;
import cz.mzk.oai.metadataRepository.repository.Identifier;
import cz.mzk.oai.metadataRepository.repository.collections.MetadataCollection;
import cz.mzk.oai.metadataRepository.repository.records.Record;
import cz.mzk.oai.metadataRepository.repository.records.presentAndDeleted.RecordsManager;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Martin Řehánek (rehan at mzk.cz)
 */
public abstract class AbstractFileRecordsManager implements RecordsManager {

    private Map<MetadataFormat, File> metadataDirMap = new HashMap<MetadataFormat, File>();
    protected final MetadataCollection collection;

    public AbstractFileRecordsManager(MetadataCollection collection) {
        this.collection = collection;
    }

    public void createRecord(Identifier identifier, MetadataFormat format) throws IOException {
        AbstractFileRecord record = getRecord(identifier, format);
        record.getMetadataFile().createNewFile();
    }

    protected File getMetadataDir(MetadataFormat format) {
        if (!metadataDirMap.containsKey(format)) {
            loadMetadataDir(format);
        }
        return metadataDirMap.get(format);
    }

    private void loadMetadataDir(MetadataFormat format) {
        File dir = buildMetadataDir(format);
        dir.mkdirs();
        metadataDirMap.put(format, dir);
    }

    private File buildMetadataDir(MetadataFormat format) {
        String name = collection.getRootDir().getAbsolutePath() + File.separator
                + format.toString() + getMetadataDirSuffix();
        return new File(name);
    }

    abstract String getMetadataDirSuffix();

    public abstract AbstractFileRecord getRecord(Identifier identifier, MetadataFormat format);

    public List<Record> getRecords(MetadataFormat format, DateStamp from, DateStamp until) {
        List<Identifier> idList = getIdentifiers(format, from, until);
        return createRecords(idList, format);
    }

    abstract List<Record> createRecords(List<Identifier> idList, MetadataFormat format);

    private List<Identifier> getIdentifiers(MetadataFormat format, DateStamp from, DateStamp until) {
        String[] filenames = getFilesModified(format, from, until);
        List<Identifier> result = new ArrayList<Identifier>(filenames.length);
        for (String filename : filenames) {
            try {
                String sysno = filename.substring(0, filename.length() - ".xml".length());
                Identifier id = Identifier.parseFileEncodedString(sysno);
                result.add(id);
            } catch (Throwable e) {
                File erroneousFile = new File(getMetadataDir(format) + File.separator + filename);
                Logger.getLogger(AbstractFileRecordsManager.class.getName()).log(Level.SEVERE, erroneousFile.getAbsolutePath(), e);
            }
        }
        return result;
    }

    public boolean contains(Identifier id, MetadataFormat metadataFormat) {
        File recordFile = getRecordFile(id, metadataFormat);
        return recordFile.exists();
    }

    private File getRecordFile(Identifier id, MetadataFormat metadataFormat) {
        AbstractFileRecord record = getRecord(id, metadataFormat);
        return record.getMetadataFile();
    }

    public void deleteRecord(Identifier id, MetadataFormat metadataFormat) throws IOException {
        File recordFile = getRecordFile(id, metadataFormat);
        boolean deleted = recordFile.delete();
        if (!deleted) {
            throw new IOException("Cannot delete file " + recordFile.getAbsolutePath());
        }
    }

    private String[] getFilesModified(MetadataFormat format, DateStamp from, DateStamp until) {
        //TODO: rozmyslet, jestli neni mozna heuristika souvisejici s casem zmeny adresare
        File metadataDir = getMetadataDir(format);
        if (from == null && until == null){
            return allFiles(metadataDir);
        } else if (from == null && until != null){
            return filesModifiedBefore(metadataDir,until);
        } else if (from != null && until == null){
            return filesModifiedAfter(metadataDir, from);
        } else {
            return filesModifiedBetween(metadataDir, from, until);
        }
    }

    private String[] allFiles(File dir) {
        return dir.list();
    }

    private boolean dirModifiedBefore(File dir, DateStamp before) {
        DateStamp dirTimeStamp = DateStamp.fromMilliseconds(dir.lastModified());
        return dirTimeStamp.isBefore(before)
                || dirTimeStamp.isEqual(before);
    }

    private String[] filesModifiedBefore(File metadataDir, final DateStamp datestamp) {
        return toFilenames(metadataDir.listFiles(new FileFilter() {

            public boolean accept(File file) {
                DateStamp fileTimeStamp = DateStamp.fromMilliseconds(file.lastModified());
                return fileTimeStamp.isBeforeOrEqual(datestamp);
            }
        }));
    }

    private String[] filesModifiedAfter(File metadataDir, final DateStamp datestamp) {
        return toFilenames(metadataDir.listFiles(new FileFilter() {

            public boolean accept(File file) {
                DateStamp fileTimeStamp = DateStamp.fromMilliseconds(file.lastModified());
                return fileTimeStamp.isAfterOrEqual(datestamp);
            }
        }));
    }

    private String[] filesModifiedBetween(File metadataDir, final DateStamp from, final DateStamp until) {
        return toFilenames(metadataDir.listFiles(new FileFilter() {

            public boolean accept(File file) {
                DateStamp fileTimeStamp = DateStamp.fromMilliseconds(file.lastModified());
                return fileTimeStamp.isAfterOrEqual(from) && fileTimeStamp.isBefore(until);
            }
        }));

    }

    private String[] toFilenames(File[] files) {
        String[] result = new String[files.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = files[i].getName();
        }
        return result;
    }
}
